/**
 * @file oled.c
 * @brief OLED驱动(ST7567)
 * @version 1.0
 * @date 2025-03-16
 * @license MIT License
 *
 * @note
 * 使用流程:
 * 1. STM32初始化IIC完成后调用OLED_Init()初始化OLED. 注意STM32启动比OLED上电快, 可等待20ms再初始化OLED
 * 2. 调用OLED_NewFrame()开始绘制新的一帧
 * 3. 调用OLED_DrawXXX()系列函数绘制图形到显存 调用OLED_Printxxx()系列函数绘制文本到显存
 * 4. 调用OLED_ShowFrame()将显存内容显示到OLED
 *
 * @note
 * 为保证中文显示正常 请将编译器的字符集设置为UTF-8
 *
 */
#include "oled.h"
#include <stdlib.h>//abs函数
#include "string.h"//memcmp函数
#include <stdarg.h>//va_list类型
#include <stdio.h>//printf函数
// OLED参数
#define OLED_PAGE 8            // OLED页数
#define OLED_ROW 8 * OLED_PAGE // OLED行数
#define OLED_COLUMN 128        // OLED列数

// OLED显存
uint8_t OLED_GRAM[OLED_PAGE][OLED_COLUMN] = {0};


// ========================== 底层通信函数 ==========================

/**
 * @brief 向OLED发送数据的函数
 * @param data 要发送的数据
 * @note 此函数是移植本驱动时的重要函数 将本驱动库移植到其他平台时应根据实际情况修改此函数
 */
void OLED_SendData(uint8_t data)
{
  uint8_t i;
  LCD_DC_HIGH();
  for (i = 0; i < 8; i++)
  {
    LCD_SCL_LOW();
    if (data >= 0x80)
      LCD_SDA_HIGH();
    else
      LCD_SDA_LOW();
    LCD_SCL_HIGH();
    data <<= 1;
  }
}

/**
 * @brief 向OLED发送指令
 * @param cmd 要发送的指令
 * @note 此函数是移植本驱动时的重要函数 将本驱动库移植到其他平台时应根据实际情况修改此函数
 */
void OLED_SendCmd(uint8_t cmd)
{
  uint8_t i;
  LCD_DC_LOW();
  for (i = 0; i < 8; i++)
  {
    LCD_SCL_LOW();
    if (cmd & 0x80)
      LCD_SDA_HIGH();
    else
      LCD_SDA_LOW();
    LCD_SCL_HIGH();
    cmd <<= 1;
  }
}

/**
 * @brief 设置OLED的页地址和列地址
 * @param page 页地址
 * @param addr 列地址
 * @note 此函数是移植本驱动时的重要函数 将本驱动库移植到其他平台时应根据实际情况修改此函数
 */
void OLED_SetAddr(uint8_t page, uint8_t addr)
{
  OLED_SendCmd(0xB0 + page);
  // addr += 0x04; // 零点存在4个像素偏差
  addr += 0x00; // 零点存在0个像素偏差
  OLED_SendCmd(0x10 + ((addr & 0x0F) >> 4));
  OLED_SendCmd(addr & 0x0F);
}

/**
 * @brief 向OLED发送数据显示一帧BMP128x64图片
 * @param bmp 图片数据
 * @note 此函数是移植本驱动时的重要函数 将本驱动库移植到其他平台时应根据实际情况修改此函数
 */
void OLED_SendBMP(uint8_t *bmp)
{
  uint8_t col = 0, page = 0;
  uint8_t temp = 0;
  LCD_CS_LOW();

  for (page = 0; page < OLED_PAGE; page++) // 8页
  {
    OLED_SetAddr(page, 0);
    for (col = 0; col < OLED_COLUMN; col++) // 128列
    {
      temp = *bmp;
      OLED_SendData(temp);
      bmp++;
    }
  }
  LCD_CS_HIGH();
}
// ========================== OLED驱动函数 ==========================

/**
 * @brief 初始化OLED (ST7567)
 * @note 此函数是移植本驱动时的重要函数 将本驱动库移植到其他驱动芯片时应根据实际情况修改此函数
 */
void OLED_Init(void)
{
  /*硬件复位*/
  LCD_RST_LOW();
  HAL_Delay(100);
  LCD_RST_HIGH();
  HAL_Delay(100);
  /* 软复位 */
  OLED_SendCmd(0xE2);
  HAL_Delay(50);

  /*片选*/
  LCD_CS_LOW();

  /*升压三部曲*/
  OLED_SendCmd(0x2c); /* 升压步骤1 */
  OLED_SendCmd(0x2e); /* 升压步骤2 */
  OLED_SendCmd(0x2f); /* 升压步骤3 */

  // 升压倍数设置连续发送
  OLED_SendCmd(0xF8); // 升压倍数选择
  OLED_SendCmd(0x00); // 0x00:4倍, 0x01:5倍, 0x10:6倍

  OLED_SendCmd(0x24); // 选择内部电阻比例(低三位有效)
  // 下两条电压指令需连续发送
  OLED_SendCmd(0x81); // 设置内部电压模式
  OLED_SendCmd(0x20); // 设置电压值(低六位有效)  //电压过高会导致屏幕颜色偏深，字迹不清晰；电压过低会导致屏幕快速刷新出现模糊

  OLED_SendCmd(0xA2); // 偏压比设置 0xa2:1/9, 0xa3:1/7
  // 旋转屏幕
  OLED_SendCmd(0xC8); // 上下行扫描方向 0xc0:正常, 0xc8:反向
  OLED_SendCmd(0xA0); // 左右列扫描方向 0xa0:正常, 0xa1:反向

  // 设置显示
  OLED_SendCmd(0xA6); // 显示 0xa6:正常, 0xa7:反显
  OLED_SendCmd(0x40); // 显示起始行n(0-63) 0x40+n
  // 开启OLED显示
  OLED_SendCmd(0xAF); // 显示 0xaf:开启, 0xae:关闭

  // 清屏
  OLED_NewFrame();

  // 开启背光
  LCD_BLK_HIGH();
}

/**
 * @brief 开启OLED显示
 */
void OLED_DisPlay_On(void)
{
  OLED_SendCmd(0x8D); // 电荷泵使能
  OLED_SendCmd(0x14); // 开启电荷泵
  OLED_SendCmd(0xAF); // 点亮屏幕
}

/**
 * @brief 关闭OLED显示
 */
void OLED_DisPlay_Off(void)
{
  OLED_SendCmd(0x8D); // 电荷泵使能
  OLED_SendCmd(0x10); // 关闭电荷泵
  OLED_SendCmd(0xAE); // 关闭屏幕
}

/**
 * @brief 设置颜色模式 黑底白字或白底黑字
 * @param ColorMode 颜色模式  COLOR_NORMAL正常    COLOR_REVERSED反色
 * @note 此函数直接设置屏幕的颜色模式
 */
void OLED_SetColorMode(OLED_ColorMode mode)
{
  if (mode == OLED_COLOR_NORMAL)
  {
    OLED_SendCmd(0xA6); // 正常显示
  }
  if (mode == OLED_COLOR_REVERSED)
  {
    OLED_SendCmd(0xA7); // 反色显示
  }
}

// ========================== 显存操作函数 ==========================
/**
 * @brief 清空显存 绘制新的一帧
 */
void OLED_NewFrame(void)
{
  
  memset(OLED_GRAM, 0X00, sizeof(OLED_GRAM));
  // //
  // uint8_t i, j;
	// for (j = 0; j < OLED_PAGE; j ++)				//遍历8页
	// {
	// 	for (i = 0; i < OLED_COLUMN; i ++)			//遍历128列
	// 	{
	// 		OLED_GRAM[j][i] = 0x00;	//将显存数组数据全部清零
	// 	}
	// }
}

/**
 * @brief 将当前显存显示到屏幕上
 * @note 此函数是移植本驱动时的重要函数 将本驱动库移植到其他驱动芯片时应根据实际情况修改此函数
 */
void OLED_ShowFrame()
{
  OLED_SendBMP((uint8_t *)OLED_GRAM);
}

/**
 * @brief 设置一个像素点
 * @param x 横坐标 0-127
 * @param y 纵坐标 0-63
 * @param color 颜色 OLED_COLOR_NORMAL 0:正常 OLED_COLOR_REVERSED 1:反显
 */
void OLED_SetPixel(uint8_t x, uint8_t y, OLED_ColorMode color)
{
  // 参数检查，保证指定像素不会超出屏幕范围
  if (x >= OLED_COLUMN || y >= OLED_ROW)
    return;
  if (!color)
  {
    OLED_GRAM[y / 8][x] |= 0X01 << (y % 8);
  }
  else
  {
    OLED_GRAM[y / 8][x] &= ~(0X01 << (y % 8));
  }
}

/**
 * @brief 设置显存中一字节数据的某几位
 * @param page 页地址 0-7
 * @param column 列地址 0-127
 * @param data 数据
 * @param start 起始位 0-7
 * @param end 结束位 0-7
 * @param color 颜色  OLED_COLOR_NORMAL 0:正常 OLED_COLOR_REVERSED 1:反显
 * @note 此函数将显存中的某一字节的第start位到第end位设置为与data相同
 * @note start和end的范围为0-7, start必须小于等于end
 * @note 此函数与OLED_SetByte_Fine的区别在于此函数只能设置显存中的某一真实字节
 */
void OLED_SetByte_Fine(uint8_t page, uint8_t column, uint8_t data, uint8_t start, uint8_t end, OLED_ColorMode color)
{
  static uint8_t temp;
  if (page >= OLED_PAGE || column >= OLED_COLUMN)
    return;
  if (color)
    data = ~data;

  temp = data | (0xff << (end + 1)) | (0xff >> (8 - start));
  OLED_GRAM[page][column] &= temp;
  temp = data & ~(0xff << (end + 1)) & ~(0xff >> (8 - start));
  OLED_GRAM[page][column] |= temp;
  // 使用OLED_SetPixel实现
  // for (uint8_t i = start; i <= end; i++) {
  //   OLED_SetPixel(column, page * 8 + i, !((data >> i) & 0x01));
  // }
}

/**
 * @brief 设置显存中的一字节数据
 * @param page 页地址 0-7 
 * @param column 列地址 0-127
 * @param data 数据
 * @param color 颜色 OLED_COLOR_NORMAL 0:正常 OLED_COLOR_REVERSED 1:反显
 * @note 此函数将显存中的某一字节设置为data的值
 */
void OLED_SetByte(uint8_t page, uint8_t column, uint8_t data, OLED_ColorMode color)
{
  if (page >= OLED_PAGE || column >= OLED_COLUMN)
    return;
  if (color)
    data = ~data;
  OLED_GRAM[page][column] = data;
}

/**
 * @brief 设置显存中的一字节数据的某几位
 * @param x 横坐标 0-127
 * @param y 纵坐标 0-63
 * @param data 数据
 * @param len 位数 1-8
 * @param color 颜色 OLED_COLOR_NORMAL 0:正常 OLED_COLOR_REVERSED 1:反显
 * @note 此函数将显存中从(x,y)开始向下数len位设置为与data相同
 * @note len的范围为1-8
 * @note 此函数与OLED_SetByte_Fine的区别在于此函数的横坐标和纵坐标是以像素为单位的, 可能出现跨两个真实字节的情况(跨页)
 */
void OLED_SetBits_Fine(uint8_t x, uint8_t y, uint8_t data, uint8_t len, OLED_ColorMode color)
{
  uint8_t page = y / 8;
  uint8_t bit = y % 8;
  if (bit + len > 8)
  {
    OLED_SetByte_Fine(page, x, data << bit, bit, 7, color);
    OLED_SetByte_Fine(page + 1, x, data >> (8 - bit), 0, len + bit - 1 - 8, color);
  }
  else
  {
    OLED_SetByte_Fine(page, x, data << bit, bit, bit + len - 1, color);
  }
  // 使用OLED_SetPixel实现
  // for (uint8_t i = 0; i < len; i++) {
  //   OLED_SetPixel(x, y + i, !((data >> i) & 0x01));
  // }
}

/**
 * @brief 设置显存中一字节长度的数据
 * @param x 横坐标 0-127
 * @param y 纵坐标 0-63
 * @param data 数据
 * @param color 颜色 OLED_COLOR_NORMAL 0:正常 OLED_COLOR_REVERSED 1:反显
 * @note 此函数将显存中从(x,y)开始向下数8位设置为与data相同
 * @note 此函数与OLED_SetByte的区别在于此函数的横坐标和纵坐标是以像素为单位的, 可能出现跨两个真实字节的情况(跨页)
 */
void OLED_SetBits(uint8_t x, uint8_t y, uint8_t data, OLED_ColorMode color)
{
  uint8_t page = y / 8;
  uint8_t bit = y % 8;
  OLED_SetByte_Fine(page, x, data << bit, bit, 7, color);
  if (bit)
  {
    OLED_SetByte_Fine(page + 1, x, data >> (8 - bit), 0, bit - 1, color);
  }
}

/**
 * @brief 设置一块显存区域
 * @param x 起始横坐标 0-127
 * @param y 起始纵坐标 0-63
 * @param data 数据的起始地址
 * @param w 宽度 1-128
 * @param h 高度 1-64
 * @param color 颜色 OLED_COLOR_NORMAL 0:正常 OLED_COLOR_REVERSED 1:反显
 * @note 此函数将显存中从(x,y)开始的w*h个像素设置为data中的数据
 * @note data的数据应该采用列行式排列
 */
void OLED_SetBlock(uint8_t x, uint8_t y, const uint8_t *data, uint8_t w, uint8_t h, OLED_ColorMode color)
{
  uint8_t fullRow = h / 8; // 完整的行数
  uint8_t partBit = h % 8; // 不完整的字节中的有效位数
  for (uint8_t i = 0; i < w; i++)
  {
    for (uint8_t j = 0; j < fullRow; j++)
    {
      OLED_SetBits(x + i, y + j * 8, data[i + j * w], color);
    }
  }
  if (partBit)
  {
    uint16_t fullNum = w * fullRow; // 完整的字节数
    for (uint8_t i = 0; i < w; i++)
    {
      OLED_SetBits_Fine(x + i, y + (fullRow * 8), data[fullNum + i], partBit, color);
    }
  }
  // 使用OLED_SetPixel实现
  // for (uint8_t i = 0; i < w; i++) {
  //   for (uint8_t j = 0; j < h; j++) {
  //     for (uint8_t k = 0; k < 8; k++) {
  //       if (j * 8 + k >= h) break; // 防止越界(不完整的字节
  //       OLED_SetPixel(x + i, y + j * 8 + k, !((data[i + j * w] >> k) & 0x01));
  //     }
  //   }
  // }
}

/// @brief 将OLED显存数组部分取反
/// @param x 起始横坐标 0-127
/// @param y 起始纵坐标 0-63
/// @param w 宽度 1-128
/// @param h 高度 1-64
/// @param color 颜色 OLED_COLOR_NORMAL 0:正常 OLED_COLOR_REVERSED 1:反显
/// @note 此函数将显存中矩形区域从(x,y)开始的w*h个像素保持不变或反色
void OLED_ReverseArea(uint8_t x, uint8_t y, uint8_t w, uint8_t h, OLED_ColorMode color)
{
  /*参数检查，保证指定区域不会超出屏幕范围*/
  if (x >= OLED_COLUMN || y >= OLED_ROW)
  {
    return;
  }

  if (x + w >= OLED_COLUMN)
  {
    w = OLED_COLUMN - x;
  }
  if (y + w >= OLED_ROW)
  {
    h = OLED_ROW - y;
  }
  /*遍历y坐标*/
  for (uint8_t j = y; j < y + h; j++)
  { /*遍历x坐标*/
    for (uint8_t i = x; i < x + w; i++)
    {
      if (color == OLED_COLOR_NORMAL)
      {
        OLED_GRAM[j / 8][i] ^= (1 << j % 8);
      }
      else
      {
        OLED_GRAM[j / 8][i] ^= (1 << j % 8);
      }
    }
  }
}
// ========================== 图形绘制函数 ==========================
/**
 * @brief 绘制一条线段
 * @param x1 起始点横坐标 0-127
 * @param y1 起始点纵坐标 0-63
 * @param x2 终止点横坐标 0-127
 * @param y2 终止点纵坐标 0-63
 * @param color 颜色 OLED_COLOR_NORMAL 0:正常 OLED_COLOR_REVERSED 1:反显
 * @note 此函数使用Bresenham算法绘制线段
 */
void OLED_DrawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, OLED_ColorMode color)
{
  static uint8_t temp = 0;
  if (x1 == x2) // 竖线
  {
    if (y1 > y2)
    {
      temp = y1;
      y1 = y2;
      y2 = temp;
    }
    for (uint8_t y = y1; y <= y2; y++)
    {
      OLED_SetPixel(x1, y, color);
    }
  }
  else if (y1 == y2) // 横线
  {
    if (x1 > x2)
    {
      temp = x1;
      x1 = x2;
      x2 = temp;
    }
    for (uint8_t x = x1; x <= x2; x++)
    {
      OLED_SetPixel(x, y1, color);
    }
  }
  else // 斜线
  {
    /*使用Bresenham算法画直线，可以避免耗时的浮点运算，效率更高*/
    /*参考文档：https://www.cs.montana.edu/courses/spring2009/425/dslectures/Bresenham.pdf*/
    /*参考教程：https://www.bilibili.com/video/BV1364y1d7Lo*/

    // Bresenham直线算法
    int16_t dx = x2 - x1;
    int16_t dy = y2 - y1;
    int16_t ux = ((dx > 0) << 1) - 1; // 判断dx的正负
    int16_t uy = ((dy > 0) << 1) - 1; // 判断dy的正负
    int16_t x = x1, y = y1, eps = 0;
    dx = abs(dx); // 取绝对值
    dy = abs(dy); // 取绝对值
    if (dx > dy)
    {
      for (x = x1; x != x2; x += ux)
      {
        OLED_SetPixel(x, y, color);
        eps += dy;
        if ((eps << 1) >= dx)
        {
          y += uy;
          eps -= dx;
        }
      }
    }
    else
    {
      for (y = y1; y != y2; y += uy)
      {
        OLED_SetPixel(x, y, color);
        eps += dx;
        if ((eps << 1) >= dy)
        {
          x += ux;
          eps -= dy;
        }
      }
    }
  }
}

/**
 * @brief 绘制一个矩形
 * @param x 起始点横坐标 0-127
 * @param y 起始点纵坐标 0-63
 * @param w 矩形宽度 1-128
 * @param h 矩形高度 1-64
 * @param color 颜色 OLED_COLOR_NORMAL 0:正常 OLED_COLOR_REVERSED 1:反显
 */
void OLED_DrawRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, OLED_ColorMode color)
{
  // 使用OLED_DrawLine实现
  OLED_DrawLine(x, y, x + w, y, color);
  OLED_DrawLine(x, y + h, x + w, y + h, color);
  OLED_DrawLine(x, y, x, y + h, color);
  OLED_DrawLine(x + w, y, x + w, y + h, color);

  // 使用OLED_SetPixel实现
  // uint8_t i,j;
  // /*遍历上下X坐标，画矩形上下两条线*/
  // for (i = x; i < x + w; i++)
  // {
  //   OLED_SetPixel(i, y, color);
  //   OLED_SetPixel(i, y + h - 1, color);
  // }
  // /*遍历左右Y坐标，画矩形左右两条线*/
  // for (j = y; j < y + h; j++)
  // {
  //   OLED_SetPixel(x, j, color);
  //   OLED_SetPixel(x + w - 1, j, color);
  // }
}

/**
 * @brief 绘制一个填充矩形
 * @param x 起始点横坐标 0-127
 * @param y 起始点纵坐标 0-63
 * @param w 矩形宽度 1-128
 * @param h 矩形高度 1-64
 * @param color 颜色   OLED_COLOR_NORMAL 0:正常 OLED_COLOR_REVERSED 1:反显
 */
void OLED_DrawFilledRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, OLED_ColorMode color)
{
  for (uint8_t i = 0; i < h; i++)
  {
    OLED_DrawLine(x, y + i, x + w, y + i, color);
  }
}

/**
 * @brief 绘制一个三角形
 * @param x1 第一个点横坐标 0-127
 * @param y1 第一个点纵坐标 0-63
 * @param x2 第二个点横坐标 0-127
 * @param y2 第二个点纵坐标 0-63
 * @param x3 第三个点横坐标 0-127
 * @param y3 第三个点纵坐标 0-63
 * @param color 颜色 OLED_COLOR_NORMAL 0:正常 OLED_COLOR_REVERSED 1:反显
 */
void OLED_DrawTriangle(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t x3, uint8_t y3, OLED_ColorMode color)
{
  OLED_DrawLine(x1, y1, x2, y2, color);
  OLED_DrawLine(x2, y2, x3, y3, color);
  OLED_DrawLine(x3, y3, x1, y1, color);
}

/**
 * @brief 绘制一个填充三角形
 * @param x1 第一个点横坐标 0-127
 * @param y1 第一个点纵坐标 0-63
 * @param x2 第二个点横坐标 0-127
 * @param y2 第二个点纵坐标 0-63
 * @param x3 第三个点横坐标 0-127
 * @param y3 第三个点纵坐标 0-63
 * @param color 颜色 OLED_COLOR_NORMAL 0:正常 OLED_COLOR_REVERSED 1:反显
 */
void OLED_DrawFilledTriangle(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t x3, uint8_t y3, OLED_ColorMode color)
{
  uint8_t a = 0, b = 0, y = 0, last = 0;
  if (y1 > y2)
  {
    a = y2;
    b = y1;
  }
  else
  {
    a = y1;
    b = y2;
  }
  y = a;
  for (; y <= b; y++)
  {
    if (y <= y3)
    {
      OLED_DrawLine(x1 + (y - y1) * (x2 - x1) / (y2 - y1), y, x1 + (y - y1) * (x3 - x1) / (y3 - y1), y, color);
    }
    else
    {
      last = y - 1;
      break;
    }
  }
  for (; y <= b; y++)
  {
    OLED_DrawLine(x2 + (y - y2) * (x3 - x2) / (y3 - y2), y, x1 + (y - last) * (x3 - x1) / (y3 - last), y, color);
  }
}

/**
 * @brief 绘制一个圆
 * @param x 圆心横坐标 0-127
 * @param y 圆心纵坐标 0-63
 * @param r 圆半径  
 * @param color 颜色  OLED_COLOR_NORMAL 0:正常 OLED_COLOR_REVERSED 1:反显
 * @note 此函数使用Bresenham算法绘制圆
 */

void OLED_DrawCircle(uint8_t x, uint8_t y, uint8_t r, OLED_ColorMode color)
{
/*使用Bresenham算法画圆，可以避免耗时的浮点运算，效率更高*/
/*参考文档：https://www.cs.montana.edu/courses/spring2009/425/dslectures/Bresenham.pdf*/
/*参考教程：https://www.bilibili.com/video/BV1VM4y1u7wJ*/

  int16_t a = 0, b = r, di = 3 - (r << 1);
  while (a <= b)
  {
    OLED_SetPixel(x - b, y - a, color);
    OLED_SetPixel(x + b, y - a, color);
    OLED_SetPixel(x - a, y + b, color);
    OLED_SetPixel(x - b, y - a, color);
    OLED_SetPixel(x - a, y - b, color);
    OLED_SetPixel(x + b, y + a, color);
    OLED_SetPixel(x + a, y - b, color);
    OLED_SetPixel(x + a, y + b, color);
    OLED_SetPixel(x - b, y + a, color);
    a++;
    if (di < 0)
    {
      di += 4 * a + 6;
    }
    else
    {
      di += 10 + 4 * (a - b);
      b--;
    }
    OLED_SetPixel(x + a, y + b, color);
  }
}

/**
 * @brief 绘制一个填充圆
 * @param x 圆心横坐标
 * @param y 圆心纵坐标
 * @param r 圆半径
 * @param color 颜色
 * @note 此函数使用Bresenham算法绘制圆
 */
void OLED_DrawFilledCircle(uint8_t x, uint8_t y, uint8_t r, OLED_ColorMode color)
{
  int16_t a = 0, b = r, di = 3 - (r << 1);
  while (a <= b)
  {
    for (int16_t i = x - b; i <= x + b; i++)
    {
      OLED_SetPixel(i, y + a, color);
      OLED_SetPixel(i, y - a, color);
    }
    for (int16_t i = x - a; i <= x + a; i++)
    {
      OLED_SetPixel(i, y + b, color);
      OLED_SetPixel(i, y - b, color);
    }
    a++;
    if (di < 0)
    {
      di += 4 * a + 6;
    }
    else
    {
      di += 10 + 4 * (a - b);
      b--;
    }
  }
}

/**
 * @brief 绘制一个椭圆
 * @param x 椭圆中心横坐标
 * @param y 椭圆中心纵坐标
 * @param a 椭圆长轴
 * @param b 椭圆短轴
 */
void OLED_DrawEllipse(uint8_t x, uint8_t y, uint8_t a, uint8_t b, OLED_ColorMode color)
{ 
  /*使用Bresenham算法画椭圆，可以避免部分耗时的浮点运算，效率更高*/
  /*参考链接：https://blog.csdn.net/myf_666/article/details/128167392*/

  int xpos = 0, ypos = b;
  int a2 = a * a, b2 = b * b;
  int d = b2 + a2 * (0.25 - b);
  while (a2 * ypos > b2 * xpos)
  {
    OLED_SetPixel(x + xpos, y + ypos, color);
    OLED_SetPixel(x - xpos, y + ypos, color);
    OLED_SetPixel(x + xpos, y - ypos, color);
    OLED_SetPixel(x - xpos, y - ypos, color);
    if (d < 0)
    {
      d = d + b2 * ((xpos << 1) + 3);
      xpos += 1;
    }
    else
    {
      d = d + b2 * ((xpos << 1) + 3) + a2 * (-(ypos << 1) + 2);
      xpos += 1, ypos -= 1;
    }
  }
  d = b2 * (xpos + 0.5) * (xpos + 0.5) + a2 * (ypos - 1) * (ypos - 1) - a2 * b2;
  while (ypos > 0)
  {
    OLED_SetPixel(x + xpos, y + ypos, color);
    OLED_SetPixel(x - xpos, y + ypos, color);
    OLED_SetPixel(x + xpos, y - ypos, color);
    OLED_SetPixel(x - xpos, y - ypos, color);
    if (d < 0)
    {
      d = d + b2 * ((xpos << 1) + 2) + a2 * (-(ypos << 1) + 3);
      xpos += 1, ypos -= 1;
    }
    else
    {
      d = d + a2 * (-(ypos << 1) + 3);
      ypos -= 1;
    }
  }
}

/**
 * @brief 绘制一张图片
 * @param x 起始点横坐标 0-127
 * @param y 起始点纵坐标 0-63
 * @param img 图片 图片数据应该采用列行式排列
 * @param color 颜色 OLED_COLOR_NORMAL 0:正常 OLED_COLOR_REVERSED 1:反显
 */
void OLED_DrawImage(uint8_t x, uint8_t y, const Image *img, OLED_ColorMode color)
{
  OLED_SetBlock(x, y, img->data, img->w, img->h, color);
}

// ================================ 文字绘制 ================================

/**
 * @brief 绘制一个ASCII字符
 * @param x 起始点横坐标  0-127
 * @param y 起始点纵坐标  0-63
 * @param ch 字符 ASCII字符
 * @param font 字体 字体数据
 * @param color 颜色  OLED_COLOR_NORMAL 0:正常 OLED_COLOR_REVERSED 1:反显
 */
void OLED_PrintASCIIChar(uint8_t x, uint8_t y, char ch, const ASCIIFont *font, OLED_ColorMode color)
{
  OLED_SetBlock(x, y, font->chars + (ch - ' ') * (((font->h + 7) / 8) * font->w), font->w, font->h, color);
}

/**
 * @brief 绘制一个ASCII字符串
 * @param x 起始点横坐标  0-127
 * @param y 起始点纵坐标  0-63
 * @param ch 字符 ASCII字符
 * @param font 字体 字体数据
 * @param color 颜色  OLED_COLOR_NORMAL 0:正常 OLED_COLOR_REVERSED 1:反显
 */
void OLED_PrintASCIIString(uint8_t x, uint8_t y, char *str, const ASCIIFont *font, OLED_ColorMode color)
{
  uint8_t x0 = x;
  while (*str)
  {
    OLED_PrintASCIIChar(x0, y, *str, font, color);
    x0 += font->w;
    str++;
  }
}

/**
 * @brief 获取UTF-8编码的字符长度
 * @param string 字符串
 * @return 字符长度
 * @note 为保证字符串中的中文会被自动识别并绘制, 需:
 * 1. 编译器字符集设置为UTF-8
 */
uint8_t _OLED_GetUTF8Len(char *string)
{
  if ((string[0] & 0x80) == 0x00)//UTF-8编码的英文字符占1个字节
  {
    return 1;
  }
  else if ((string[0] & 0xE0) == 0xC0)//UTF-8编码的中文字符占2个字节
  {
    return 2;
  }
  else if ((string[0] & 0xF0) == 0xE0)///UTF-8编码的中文字符占3个字节
  {
    return 3;
  }
  else if ((string[0] & 0xF8) == 0xF0)//UTF-8编码的中文字符占4个字节
  {
    return 4;
  }
  return 0;
}

/**
 * @brief 绘制字符串支持中文
 * @param x 起始点横坐标  0-127
 * @param y 起始点纵坐标  0-63
 * @param str 字符串 UTF-8编码
 * @param font 字体 字体数据
 * @param color 颜色  OLED_COLOR_NORMAL 0:正常 OLED_COLOR_REVERSED 1:反显
 *
 * @note 为保证字符串中的中文会被自动识别并绘制, 需:
 * 1. 编译器字符集设置为UTF-8
 * 2. 使用波特律动LED取模工具生成字模(https://led.baud-dance.com)
 */
void OLED_PrintString(uint8_t x, uint8_t y, char *str, const Font *font, OLED_ColorMode color)
{
  uint16_t i = 0;                                       // 字符串索引
  uint8_t oneLen = (((font->h + 7) / 8) * font->w) + 4; // 一个字模占多少字节
  uint8_t found;                                        // 是否找到字模
  uint8_t utf8Len;                                      // UTF-8编码长度
  uint8_t *head;                                        // 字模头指针
  while (str[i])
  {
    found = 0;
    utf8Len = _OLED_GetUTF8Len(str + i);
    if (utf8Len == 0)
      break; // 有问题的UTF-8编码

    // 寻找字符  TODO 优化查找算法, 二分查找或者hash
    for (uint8_t j = 0; j < font->len; j++)
    {
      head = (uint8_t *)(font->chars) + (j * oneLen);
      if (memcmp(str + i, head, utf8Len) == 0)//是C语言中的一个函数，用于比较两个内存区域的前n个字节。
      {
        OLED_SetBlock(x, y, head + 4, font->w, font->h, color);
        // 移动光标
        x += font->w;
        i += utf8Len;
        found = 1;
        break;
      }
    }

    // 若未找到字模,且为ASCII字符, 则缺省显示ASCII字符
    if (found == 0)
    {
      if (utf8Len == 1)
      {
        OLED_PrintASCIIChar(x, y, str[i], font->ascii, color);
        // 移动光标
        x += font->ascii->w;
        i += utf8Len;
      }
      else
      {
        OLED_PrintASCIIChar(x, y, ' ', font->ascii, color);
        x += font->ascii->w;
        i += utf8Len;
      }
    }
  }
}

/**
 * @brief 使用printf格式化字符串并绘制
 * @param x 起始点横坐标  0-127
 * @param y 起始点纵坐标  0-63
 * @param font 字体 字体数据
 * @param color 颜色  OLED_COLOR_NORMAL 0:正常 OLED_COLOR_REVERSED 1:反显
 * @param format 格式化字符串 
 * @param ... 可变参数
 */
void OLED_PrintfString(uint8_t x, uint8_t y, const Font *font, OLED_ColorMode color, const char *format, ...)
{
    char buffer[128]; // 假设最大字符串长度为128
    va_list args;//定义一个va_list类型的变量，用来存储单个参数
    va_start(args, format);//初始化va_list变量，使其指向第一个可变参数
    vsnprintf(buffer, sizeof(buffer), format, args);//将可变参数按照format格式化成字符串
    va_end(args);//释放va_list变量
    OLED_PrintString(x, y, buffer, font, color);//打印字符串
}

